package mcommand;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.text.DecimalFormat;
import java.util.ArrayList;

import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;

import mcommand.Event.EventType;
import mcommand.MissileBay.MissileBayState;

/**
 * Board Class this class organizes how the board should look like and handles
 * when a object of another class should be created, used... it's like the
 * controlling center of the game
 *
 * @author Marcel, Christian, Klaus
 */
@SuppressWarnings("serial")
public class Board extends JPanel {

    // Defines the refresh rate of the panel, here 20 ms
    public static final int DELAY = 20;
    
    // Initiates the variable for the debug mode
    public static boolean DEBUG;
    
    // initiates a variable to check if we use eastereggs
    private static boolean eastereggs;

    // Path to sound files
    private static final String PATH_SOUND_READY = "assets/ready.wav";
    private static final String PATH_SOUND_GAMEOVER = "assets/gameover.wav";
    private static final String PATH_SOUND_EMPTYAMMO = "assets/empty.wav";
    private static final String PATH_SOUND_PAUSE = "assets/pause.mid";
    
    // Locale strings
    private final String LOCALE_READY = "<Spacebar if ready!>";
    private final String LOCALE_PAUSE1 = "<Game Paused, P to continue.>";
    private final String LOCALE_PAUSE2 = "<Press S to Save and Continue later>";
    private final String LOCALE_LEVELCOMPLETED1 = "Level ";
    private final String LOCALE_LEVELCOMPLETED2 = " completed.";
    private final String LOCALE_SPACEBAR = "<Spacebar>";
    private final String LOCALE_GAMEOVER = "<Game Over>";
    
    // Defines the SoundHandlers
    private SoundHandler soundGameOver;
    private SoundHandler soundReady;
    private SoundHandler emptyAmmo;
    public static SoundHandler paused;


    // defines constants of the border of the frame
    private static final int FRAMEBORDER_HEIGHT = 28;
    private static final int FRAMEBORDER_WIDTH = 6;
    private static final int IMAGE_GROUND_OFFSET_Y = 60;

    // Position of the missile-bays
    private final int MBAY_LEFT_X = 17;
    private final int MBAY_LEFT_Y = 516;
    private final int MBAY_MID_X = 400;
    private final int MBAY_MID_Y = 516;
    private final int MBAY_RIGHT_X = 781;
    private final int MBAY_RIGHT_Y = 516;

    // Position of the labels from the missile-bays
    private final int MBAY_LEFT_LABEL_X = 6;
    private final int MBAY_LEFT_LABEL_Y = 537;
    private final int MBAY_MID_LABEL_X = 388;
    private final int MBAY_MID_LABEL_Y = 537;
    private final int MBAY_RIGHT_LABEL_X = 770;
    private final int MBAY_RIGHT_LABEL_Y = 537;

    // Integer array of the city positions
    private final int NUM_CITIES = 6;
    private final int[] CITY_POSITIONS_X = new int[] { 41, 141, 241, 468, 568,
            668 };
    private final int[] CITY_POSITIONS_Y = new int[] { 498, 498, 498, 498, 498,
            498 };

    // GameInterface positions
    private final int GAMEINTERFACE_X = 700;
    private final int GAMEINTERFACE_Y = 0;
    private final int GAMEINTERFACE_OFFSET = 18;
    private final int GAMEINTERFACE_MESSAGE_ROW1 = 280;

    // GameInterface String rowcounts
    private final int GAMEINTERFACE_ROW_PLAYERNAME = 1;
    private final int GAMEINTERFACE_ROW_LEVEL = 2;
    private final int GAMEINTERFACE_ROW_POINTS = 3;
    private final int GAMEINTERFACE_ROW_DEBUG = 5;
    private final int GAMEINTERFACE_ROW_TIME = 6;
    private final int GAMEINTERFACE_ROW_CITIES = 7;
    private final int GAMEINTERFACE_ROW_S_ROCKETS = 8;
    private final int GAMEINTERFACE_ROW_S_ROCKETSHIT = 9;

    private final int GAMEINTERFACE_ROW_READY = 0;
    private final int GAMEINTERFACE_ROW_PAUSE1 = 0;
    private final int GAMEINTERFACE_ROW_PAUSE2 = 1;
    private final int GAMEINTERFACE_ROW_LEVELCOMPLETED = 0;
    private final int GAMEINTERFACE_ROW_SPACEBARHINT = 4;
    private final int GAMEINTERFACE_ROW_GAMEOVER = 0;

    // Sets how many bonus points you get after every level
    private final int BONUSPOINTS_CITY = 50;
    private final int BONUSPOINTS_MISSILE = 1;

    // refreshes the panel
    private Timer timer;

    // represents the current game state (enum)
    private GameState gameState;

    // holds panel size
    private int width;
    private int height;

    // sets default values for playername and points
    private String playername = "undefined";
    private static int points = 0;

    // sets the empty variables for the bonus text after you finished a level
    // for example 3x city survived, 3x50 points = 150 points
    private String bonusString_cities = "";
    private String bonusString_missiles = "";

    // defines a ArrayList for the enemy and for the user where missiles can
    // be saved into
    private ArrayList<AllyMissile> am;
    private ArrayList<EnemyMissile> em;

    // defines a ArrayList for missile bays and cities where these objects can
    // be saved into
    private ArrayList<MissileBay> missileBays;
    private ArrayList<City> cities;

    // defines where the ground image can be found and initiates some ground
    // specific variables
    private String PATH_GROUND = "assets/ground800.png";
    private final String PATH_SKY = "assets/skyBackground.png";
    private int ground_y = 0;
    private final int GROUND_X = 0;
    protected static Image ground;
    protected static Image sky;

    // defines a CollisionHandler and a LevelController
    private CollisionHandler collisionHandler;
    private LevelController levelController;

    // a temporary game state - used for pause function
    GameState tempState;

    // parent Frame
    MCommand parent;

    // save function
    private boolean gameContinued;
    private ToolXMLReader xmlreader;

    // stores point count at start of level
    private int pointsAtStart = 0;

    // for the statistics function
    private Statistics stats;
    private StatisticsHandler stats_handler;

    /**
     * this constructor initiates the board layout.
     * 
     * @param parent parentFrame
     * @param isContinued wether it's new game or from saved file
     */
    public Board(final MCommand parent, final boolean isContinued) {

        // sets the DEBUG value to false at the beginning
        DEBUG = false;
        
        // new SoundHandler for pause mode
        paused = new SoundHandler(PATH_SOUND_PAUSE);

        // sets the boolean value if this game is continued or not
        this.gameContinued = isContinued;

        // sets up a new xml reader object
        this.xmlreader = new ToolXMLReader();

        // sets up a new statistic object
        this.stats = new Statistics(this);

        // sets up a new statistics handler
        this.stats_handler = new StatisticsHandler();

        // adds to the statistics handler a new observer with
        // the statistic object
        this.stats_handler.addObserver(stats);

        // saves the parent component
        this.parent = parent;

        // sets the game state to "Loading"
        gameState = GameState.LOADING;

        // creates arraylists for the enemy missiles and the ally missiles
        am = new ArrayList<AllyMissile>();
        em = new ArrayList<EnemyMissile>();

        // calls the setup method for the cities and missile bays
        setupCities();
        setupMissileBays();

        // creates a new collisionHandler
        collisionHandler = new CollisionHandler(stats_handler);

        // creates a new LevelController for this board
        levelController = new LevelController(this);

        // if there is no ground image added, add it now, this catches a
        // exception if the ground file could
        // not be loaded, and give a message on the console
        if (ground == null) {
            try {
                ImageIcon ii = new ImageIcon(this.getClass().getResource(
                        PATH_GROUND));
                ground = ii.getImage();
            } catch (Exception e) {
                System.err.println("Ground could not be loaded");
            }
        }

        // adds a keylistener to react to keystrokes in update()
        this.addKeyListener(new TAdapter());

        // sets it focusable (now its able to catch the cursor)
        this.setFocusable(true);

        // defines that the background is black
        this.setBackground(Color.BLACK);

        // defines the width and height without the borders and the caption
        // line
        width = parent.SCREEN_X - FRAMEBORDER_WIDTH;
        height = parent.SCREEN_Y - FRAMEBORDER_HEIGHT;
        
        // defines the screenWidth in StringPrinter
        StringPrinter.setScreenWidth(width);

        // defines the y-value for the ground image
        ground_y = height - IMAGE_GROUND_OFFSET_Y;

        // resets Counter
        resetPoints();
        
        // resets all eastereggs if used in a previous game
        if (eastereggs) {
            resetEastereggs();
        }
        
        // if the game is continued, it loads the saved points
        // out of the xml file
        if (gameContinued) {
            addPoints(xmlreader.readScoreFromXML());
        }

        // if the game continues, the last saved level is loaded
        // out of the xml file, if it's not continued it start at
        // level 1
        if (gameContinued) {
            loadLevel(xmlreader.readLevelFromXML());
        } else {
            loadLevel(1);
        }

        // adds a new timer with the declared delay value and creates a new
        // update object
        // then starts the timer
        timer = new Timer(DELAY, new Update());
        timer.start();

        // changes the gamestate to "WAITING"
        gameState = GameState.WAITING;

        // calls the setup method to play the ready sound
        // when ready
        setupSound();
    }
    
    /**
     * Disables the eastereggs and sets all to default. 
     */
    private void resetEastereggs() {
        // resets the file pith for the game images to the default ones
        PATH_GROUND = "assets/ground800.png";
        City.PATH_IMAGE_CITYALIVE = "assets/city.png";
        City.PATH_IMAGE_CITYDESTROYED = "assets/citydestroyed.png";
        // tries to load the default images
        try {
            // get the images from the resources
            ImageIcon ii = new ImageIcon(this.getClass().getResource(
                    PATH_GROUND));
            ImageIcon ca = new ImageIcon(this.getClass().getResource(
                    City.PATH_IMAGE_CITYALIVE));
            ImageIcon cd = new ImageIcon(this.getClass().getResource(
                    City.PATH_IMAGE_CITYDESTROYED));
            // tries to load the images into the variables
            ground = ii.getImage();
            City.cityAlive = ca.getImage();
            City.cityDestroyed = cd.getImage();
            
            // disables the sky feature (background image)
            sky = null;
            
            // restores the default missile sound
            Missile.PATH_SOUND_EXPLOSION = "assets/gun.wav";
            
            // defines now that no eastereggs are used because all
            // is defualt
            eastereggs = false;
            
          // if DEBUG mode is on a error is printed out if something
          // failed while resetting to default.
        } catch (Exception e) {
            if (DEBUG) {
                System.err.println("Reset of one or more eastereggs failed");
            }
        }
    }
    
    /**
     * Draws all objects on the screen.
     * 
     * @param g graphics object of panel
     */
    @Override
    public final void paint(final Graphics g) {
        super.paint(g);
        
        // turns antialiasing on, looks nicer
        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);

        if (sky != null) {
            g2d.drawImage(sky, 0, 0, null);
        }
        
        // draws the ground
        g2d.drawImage(ground, GROUND_X, ground_y, null);

        // draws the cities
        for (int i = 0; i < cities.size(); i++) {
            cities.get(i).paint(g2d);
        }

        // draws the enemy missiles
        g2d.setColor(Color.WHITE);
        for (int i = 0; i < em.size(); i++) {
            em.get(i).paint(g2d);
        }

        // draws the user missiles
        for (int i = 0; i < am.size(); i++) {
            am.get(i).paint(g2d);
        }
        
        
        // draws the interface.Shows the level, the time, the cities and the
        // points
        // (GAMEINTERFACE_OFFSET * x) -> x defines the rowcount
        g2d.drawString(playername, GAMEINTERFACE_X,
                (GAMEINTERFACE_Y + GAMEINTERFACE_OFFSET
                        * GAMEINTERFACE_ROW_PLAYERNAME));
        g2d.drawString("Level: " + levelController.getLevelCount(),
                GAMEINTERFACE_X, (GAMEINTERFACE_Y + GAMEINTERFACE_OFFSET
                        * GAMEINTERFACE_ROW_LEVEL));
        g2d.drawString("Points: " + getPoints(), GAMEINTERFACE_X,
                (GAMEINTERFACE_Y + GAMEINTERFACE_OFFSET
                        * GAMEINTERFACE_ROW_POINTS));

        // if DEBUG mode is on, it shows also the time, the alive cities
        // and the statistics live in the game
        if (DEBUG) {
            g2d.drawString("=== DEBUG ===", GAMEINTERFACE_X,
                    (GAMEINTERFACE_Y + GAMEINTERFACE_OFFSET
                            * GAMEINTERFACE_ROW_DEBUG));
            DecimalFormat df = new DecimalFormat("0.00");
            g2d.drawString(
                    "Time LC: " + df.format(levelController.getTimeInLevel()),
                    GAMEINTERFACE_X, (GAMEINTERFACE_Y + GAMEINTERFACE_OFFSET
                            * GAMEINTERFACE_ROW_TIME));
            g2d.drawString("Cities: " + getAliveCitiesCount(), GAMEINTERFACE_X,
                    (GAMEINTERFACE_Y + GAMEINTERFACE_OFFSET
                            * GAMEINTERFACE_ROW_CITIES));
            g2d.drawString("S_Rocket: " + Statistics.getRocketCounter(), 
                    GAMEINTERFACE_X, 
                    (GAMEINTERFACE_Y + GAMEINTERFACE_OFFSET 
                            * GAMEINTERFACE_ROW_S_ROCKETS));
            g2d.drawString("S_RocketHit: " + Statistics.getRocketHitCounter(), 
                    GAMEINTERFACE_X, 
                    (GAMEINTERFACE_Y + GAMEINTERFACE_OFFSET 
                            * GAMEINTERFACE_ROW_S_ROCKETSHIT));
        }

        // draws the missile bays ammo
        if (gameState == GameState.RUNNING) {
            for (int i = 0; i < missileBays.size(); i++) {
                missileBays.get(i).paint(g2d);
            }
        }
        
        // draws String for the WAITING game state
        if (gameState == GameState.WAITING) {
            StringPrinter.drawStringCentered(g2d, LOCALE_READY, 
                    GAMEINTERFACE_MESSAGE_ROW1 
                    + GAMEINTERFACE_OFFSET * GAMEINTERFACE_ROW_READY);
        }
 
        // draws String for the PAUSED game state
        if (gameState == GameState.PAUSED) {
            StringPrinter.drawStringCentered(g2d, LOCALE_PAUSE1,  
                    GAMEINTERFACE_MESSAGE_ROW1 
                    + GAMEINTERFACE_OFFSET * GAMEINTERFACE_ROW_PAUSE1);
            StringPrinter.drawStringCentered(g2d, LOCALE_PAUSE2,  
                    GAMEINTERFACE_MESSAGE_ROW1 
                    + GAMEINTERFACE_OFFSET * GAMEINTERFACE_ROW_PAUSE2);
        }
        
        // draws the Info-Strings for the ENDOFLEVEL game state
        if (gameState == GameState.ENDOFLEVEL) {
            StringPrinter.drawStringCentered(g2d, 
                    LOCALE_LEVELCOMPLETED1 + levelController.getLevelCount()
                    + LOCALE_LEVELCOMPLETED2, 
                    GAMEINTERFACE_MESSAGE_ROW1 
                    + GAMEINTERFACE_OFFSET 
                    * GAMEINTERFACE_ROW_LEVELCOMPLETED);
            StringPrinter.drawStringCentered(g2d, bonusString_cities, 
                    GAMEINTERFACE_MESSAGE_ROW1 
                    + GAMEINTERFACE_OFFSET 
                    * (GAMEINTERFACE_ROW_LEVELCOMPLETED + 1));
            StringPrinter.drawStringCentered(g2d, bonusString_missiles, 
                    GAMEINTERFACE_MESSAGE_ROW1 
                    + GAMEINTERFACE_OFFSET 
                    * (GAMEINTERFACE_ROW_LEVELCOMPLETED + 2));
            StringPrinter.drawStringCentered(g2d, LOCALE_SPACEBAR, 
                    GAMEINTERFACE_MESSAGE_ROW1 
                    + GAMEINTERFACE_OFFSET * GAMEINTERFACE_ROW_SPACEBARHINT);
        }

        // draws String for the GAMEOVER game state
        if (gameState == GameState.GAMEOVER) {
            StringPrinter.drawStringCentered(g2d, LOCALE_GAMEOVER, 
                    GAMEINTERFACE_MESSAGE_ROW1 
                    + GAMEINTERFACE_OFFSET * GAMEINTERFACE_ROW_GAMEOVER);
        } 
    }

    /**
     * Checks if eastereggs should be enabled.
     */
    private void checkEastereggs() {
        // turns the DEBUG mode on an marks that easterggs are used
        if (playername.contains("debug")) {
            // deletes the easteregg code from the playername
            playername = playername.replace("debug", "");
            DEBUG = true;
            eastereggs = true;
        }
        
        // sets the playername to the initials of the developer
        if (playername.contains("developers")) {
            playername = "KN CM MW";
            eastereggs = true;
        }
        
        // sets the background to the early night one (gray)
        if (playername.contains("early_night")) {
            setBackground(Color.GRAY);
            playername = playername.replace("early_night", "");
            eastereggs = true;
        }
        
        // sets the background to the morning one (orange)
        if (playername.contains("morning")) {
            setBackground(Color.ORANGE);
            playername = playername.replace("morning", "");
            eastereggs = true;
        }
        
        // sets the background to the console style (light green)
        if (playername.contains("console")) {
            setBackground(Color.GREEN);
            playername = playername.replace("console", "");
            eastereggs = true;
        }
        
        // changes the missile sound to the hardcore one
        if (playername.contains("hardcore")) {
            Missile.PATH_SOUND_EXPLOSION = "assets/hardcore.wav";
            playername = playername.replace("hardcore", "");
            eastereggs = true;
        }    
        
        // changes the missile sound to a funny sound
        if (playername.contains("funny")) {
            Missile.PATH_SOUND_EXPLOSION = "assets/funny.wav";
            playername = playername.replace("funny", "");
            eastereggs = true;
        }
        
        // loads a space theme with many components
        if (playername.contains("space")) {
            // loads the paths for the images of the theme
            City.PATH_IMAGE_CITYALIVE = "assets/city_space.png";
            City.PATH_IMAGE_CITYDESTROYED = "assets/city_space_destroyed.png";
            PATH_GROUND = "assets/ground800_moon.png";
            
            // tries to load the images
            try {
                ImageIcon ii = new ImageIcon(this.getClass().getResource(
                        PATH_GROUND));
                ImageIcon ca = new ImageIcon(this.getClass().getResource(
                        City.PATH_IMAGE_CITYALIVE));
                ImageIcon cd = new ImageIcon(this.getClass().getResource(
                        City.PATH_IMAGE_CITYDESTROYED));
                ground = ii.getImage();
                City.cityAlive = ca.getImage();
                City.cityDestroyed = cd.getImage();
                
            } catch (Exception e) {
                if (DEBUG) {
                    System.err.println("One or more easteregg images"
                		+ "could not be loaded");
                }
            }
            
            // deletes the easteregg code out of the playername
            playername = playername.replace("space", "");
            eastereggs = true;
        }
        
        // loads the sky background easteregg
        if (playername.contains("sky")) {
            try {
                ImageIcon skyline = new ImageIcon(this.getClass().getResource(
                        PATH_SKY));
                sky = skyline.getImage();
            } catch (Exception e) {
                if (DEBUG) {
                    System.err.println("Sky image could not be loaded");
                }
            }
            playername = playername.replace("sky", "");
        }
        
        // if a player just entered eastereggs or no name, he is called
        // in the game nameless
        if (playername.equals("")) {
            playername = "nameless";
            eastereggs = true;
        }
    }

    /**
     * Sets up 6 Cities on CITY_POSITION_ positions.
     */
    private void setupCities() {
        // if the game is not continued, new ArrayList for
        // cities is created and new cities with position
        // are saved into
        if (!gameContinued) {
            cities = new ArrayList<City>();

            for (int i = 0; i < NUM_CITIES; i++) {
                City city = new City();
                city.setPosition(CITY_POSITIONS_X[i], CITY_POSITIONS_Y[i]);
                cities.add(city);
            }
        // if the game is continued it loads the cities aut of
        // the saved xml file
        } else {
            cities = xmlreader.readCitiesFromXML();

            for (int i = 0; i < cities.size(); i++) {
                cities.get(i).setPosition(CITY_POSITIONS_X[i],
                        CITY_POSITIONS_Y[i]);
            }
        }
    }

    /**
     * Sets up 3 missile bays on hardcoded positions, sets positions
     * of the labels for the rocketcounters.
     */
    private void setupMissileBays() {
        // new ArrayList for missile bays
        missileBays = new ArrayList<MissileBay>();
        
        // adds new missile bases with the hardcoded positions
        missileBays.add(new MissileBay(MBAY_LEFT_X, MBAY_LEFT_Y));
        missileBays.add(new MissileBay(MBAY_MID_X, MBAY_MID_Y));
        missileBays.add(new MissileBay(MBAY_RIGHT_X, MBAY_RIGHT_Y));

        // sets the label positions for every missile bay
        missileBays.get(0).setLabelPosition(MBAY_LEFT_LABEL_X,
                MBAY_LEFT_LABEL_Y);
        missileBays.get(1).setLabelPosition(MBAY_MID_LABEL_X, 
                MBAY_MID_LABEL_Y);
        missileBays.get(2).setLabelPosition(MBAY_RIGHT_LABEL_X,
                MBAY_RIGHT_LABEL_Y);
    }

    /**
     * Sets up the sounds, and play the ready sound on startup.
     */
    private void setupSound() {
        // sets up a new SoundHandler fot the ready sound
        soundReady = new SoundHandler(PATH_SOUND_READY);
        
        // plays the ready sound
        soundReady.playSoundOnce();
        
        // sets up a new SoundHandler fot the game over sound
        soundGameOver = new SoundHandler(PATH_SOUND_GAMEOVER);
    }

    /**
     * Return the points.
     * @return returns the current points
     */
    public static int getPoints() {
        return points;
    }

    /**
     * Adds / decrements a specific number of points.
     * 
     * @param apoints added value
     */
    public static void addPoints(final int apoints) {
        points += apoints;
    }

    /**
     * Sets current points to Zero (reset).
     */
    public static void resetPoints() {
        points = 0;
    }

    /**
     * Sets a new playername 
     * (shown in the upper right corner and on highscore).
     *
     * @param value (new Playername)
     */
    public final void setPlayername(final String value) {
        this.playername = value;
        if (playername.equals("")) {
            playername = "nameless";
        }
       
        checkEastereggs();
    }

    /**
     * Returns the current playername.
     * @return playername
     */
    public final String getPlayername() {
        return playername;
    }

    /**
     * After every won level, bonus points will be added the standard for city.
     * is 50 points and every missile 1 point
     */
    public final void calculateBonusPoints() {
        int aliveCities = 0;
        int remainingMissiles = 0;

        // for every alive city, to the aliveCities value will be added 1
        for (City city : cities) {
            if (city.isAlive()) {
                aliveCities++;
            }
        }
        // adds to the remainingMissiles value the left missiles for every
        // missile bay
        for (MissileBay missileBay : missileBays) {
            remainingMissiles += missileBay.getRocketCount();
        }
        // defines how the bonus strings for cities and missiles will look like
        bonusString_cities = aliveCities + " Cities x " + BONUSPOINTS_CITY
                + " = " + (aliveCities * BONUSPOINTS_CITY);
        bonusString_missiles = remainingMissiles + " Rockets x "
                + BONUSPOINTS_MISSILE + " = "
                + (remainingMissiles * BONUSPOINTS_MISSILE);

        // adds to the points, the bonus points for the left cities and
        // remaining missiles
        addPoints((aliveCities * BONUSPOINTS_CITY)
                + (remainingMissiles * BONUSPOINTS_MISSILE));
    }

    /**
     * if the missile bay has one or more missiles left, a new missile will be
     * created and the start position is the missile bay. The target is the
     * mouse pointer position the missile will be added to the users missile
     * arraylist. If the missile bay is empty the noAmmo method is called.
     * 
     * @param missileBay missileBay
     */
    private void createAllyMissile(final MissileBay missileBay) {
        if (missileBay.decreaseRocketCount()) {
            // creates a new missile
            AllyMissile aMissile = new AllyMissile(this);

            // get the MousePointer positions
            int MouseX = this.getMousePosition().x;
            int MouseY = this.getMousePosition().y;

            // sets the target for the missile where the mouse pointer is
            aMissile.setTarget(MouseX, MouseY);

            // sets that the missile should start where the missile bay is
            aMissile.setStart(missileBay.getX(), missileBay.getY());

            // adds the missile to the user missile arraylist
            am.add(aMissile);

            stats_handler.rocketFired();
        } else {
            // missile bay out of ammo
            noAmmo(missileBay);
        }
    }

    /**
     * if debug mode is on, this method prints on the console out which missile.
     * bay is empty
     * 
     * @param missileBay missileBay
     */
    private void noAmmo(final MissileBay missileBay) {
        // creates a new soundhandler for the empty missile bay sound
        emptyAmmo = new SoundHandler(PATH_SOUND_EMPTYAMMO);
        // plays the sound for no ammo once
        emptyAmmo.playSoundOnce();
        // defines the String for msg
        String msg = "Keine Munition mehr in MBay ";

        // gets the index of the missile bay
        int mbIndex = missileBays.indexOf(missileBay);

        // looks which case for mbindex and adds the msg which missile bay is
        // ment
        switch (mbIndex) {
        case 0:
            msg += "Links";
            break;
        case 1:
            msg += "Mitte";
            break;
        case 2:
            msg += "Rechts";
            break;
        default:
            msg += "Unbekannt";
            break;
        }

        // if debug mode is on then the msg String is printed out on the console
        if (DEBUG) {
            System.out.println(msg);
        }
    }

    /**
     * creates a enemy missile with a random target.
     */
    private void createEnemyMissile() {
        // creates a new enemy missile
        EnemyMissile eMissile = new EnemyMissile(this);

        // calls the random target method of the enemy missile
        eMissile.randomTarget();

        // adds the missile to the arraylist of the enemy missiles
        em.add(eMissile);
    }

    /**
     * Handles if keys are pressed and what happens then p for pause mode y for
     * new missiles for left missile bay by x for new missiles for middle
     * missile bay c for new missiles for right missile bay crtl creates a new
     * enemy missile if game is in debug mode. space bar for beginning or next
     * level.
     * 
     * 
     */
    private class TAdapter extends KeyAdapter {

        /**
         * If key is pressed.
         * 
         * @param e e
         */
        public void keyPressed(final KeyEvent e) {

            // adds the keycode to keyCode
            int keyCode = e.getKeyCode();

            // if the game state is RUNNING
            if (gameState == GameState.RUNNING) {

                // if keycode equals the keycode for the Y-key
                if (keyCode == KeyEvent.VK_Y) {

                    // creates a new user missile for the left missile bay
                    try {
                        createAllyMissile(missileBays.get(0));
                    } catch (Exception c) {
                        if (DEBUG) {
                            System.out.println("neben Fenster geschossen"
                                    + " bei linker missile Bay");
                        }
                    }
                }
                // if keycode equals the keycode for the X-key
                if (keyCode == KeyEvent.VK_X) {

                    // creates a new user missile for the middle missile bay
                    try {
                        createAllyMissile(missileBays.get(1));
                    } catch (Exception c) {
                        if (DEBUG) {
                            System.out
                                    .println("neben Fenster geschossen"
                                            + " bei mittlerer missile Bay");
                        }
                    }
                }
                // if keycode equals the keycode for the C-key
                if (keyCode == KeyEvent.VK_C) {

                    // creates a new user missile for the right missile bay
                    try {
                        createAllyMissile(missileBays.get(2));
                    } catch (Exception c) {
                        if (DEBUG) {
                            System.out.println("neben Fenster"
                                 + " geschossen bei rechter missile Bay");
                        }
                    }
                }
                // if the ctrl key is pressed and debug mode is turned on, a new
                // enemy missile is created
                if (keyCode == KeyEvent.VK_CONTROL && DEBUG) {

                    // creates a new enemy missile
                    createEnemyMissile();
                }
            }
            // if space bar is pressed
            if (keyCode == KeyEvent.VK_SPACE) {

                // if the game state is WAITING
                if (gameState == GameState.WAITING) {

                    // changes the game state to running
                    gameState = GameState.RUNNING;
                }

                // if game state is ENDOFLEVEL
                if (gameState == GameState.ENDOFLEVEL) {

                    // to the getLevelCount() value is added 1 and the game
                    // state changes to RUNNING
                    if (loadLevel(levelController.getLevelCount() + 1)) {
                        gameState = GameState.RUNNING;
                    } else {
                        parent.submitHighscore(playername, points);
                        gameState = GameState.GAMEOVER;
                    }
                }
            }
            // if the key p is pressed
            if (keyCode == KeyEvent.VK_P) {

                // game state changes into PAUSED
                if (gameState != GameState.PAUSED) {

                    // plays the pause sound
                    paused.playSound();

                    // saves the current game state
                    tempState = gameState;
                    gameState = GameState.PAUSED;

                } else {
                    // stops the pause sound when back from pause mode
                    paused.stopSound();

                    // if game state is already paused then it's now the
                    // previous game state
                    gameState = tempState;
                }
            }
            // if the key s is pressed
            if (keyCode == KeyEvent.VK_S) {

                // if the game state is paused
                if (gameState == GameState.PAUSED) {

                    // writes the playername, points from the start
                    // of the level, the level and the cites into
                    // the xml file
                    ToolXMLWriter.writeGameStateIntoXML(playername,
                            pointsAtStart, levelController.getLevelCount(),
                            cities);
                    // sets the gamestate to game over
                    gameState = GameState.GAMEOVER;
                }
            }
        }
    }

    /**
     * This method brings the game one step further. (one frame delay)
     * 
     */
    private class Update implements ActionListener {
        /**
         * If a action is performed, it brings the game
         * one step further.
         * 
         * @param e e
         */
        public void actionPerformed(final ActionEvent e) {
            if (gameState == GameState.RUNNING) {
                // if no city is alive the game state changes to GAMEOVER and
                // the game over sound is played
                if (getAliveCitiesCount() < 1) {
                    gameState = GameState.GAMEOVER;
                    parent.submitHighscore(playername, points);
                    soundGameOver.playSoundOnce();
                    return;
                }
                // calls the update method of every enemy missile
                for (int i = 0; i < em.size(); i++) {
                    EnemyMissile eMissile = em.get(i);
                    eMissile.update();

                    // if missile is destroyed, it will be removed of the "em"
                    // ArrayList
                    if (eMissile.isDestroyed()) {
                        em.remove(i);
                    }
                }
                // calls the update method of every missile bay
                for (int i = 0; i < missileBays.size(); i++) {
                    missileBays.get(i).update();
                }

                // calls the update method of every user missile
                for (int i = 0; i < am.size(); i++) {
                    AllyMissile aMissile = am.get(i);
                    aMissile.update();

                    // if missile is destroyed, it will be removed of the "am"
                    // arraylist
                    if (aMissile.isDestroyed()) {
                        am.remove(i);
                    }
                }
                // updates the collision handler if a missile hits another
                // missile or city
                collisionHandler.update(em, am, cities, missileBays);

                // calls the update method of the levelController
                levelController.update(DELAY / 1000.0f);
            }
            // repaints the board
            repaint();
        }
    }

    /**
     * The possible game states.
     */
    public enum GameState {
        LOADING,
        WAITING,
        RUNNING,
        ENDOFLEVEL,
        PAUSED,
        GAMEOVER
    }

    /**
     * Returns the width of the board without captionline and window borders.
     * 
     * @return board width
     */
    public final int getBoardWidth() {
        return width;
    }

    /**
     * Returns the height of the board without captionline and window borders.
     * 
     * @return board height
     */
    public final int getBoardHeight() {
        return height;
    }

    /**
     * Method to set the game state.
     * 
     * @param gameState gameState
     */
    public final void setGameState(final GameState gameState) {
        this.gameState = gameState;
    }

    /**
     * Method to get the game state.
     * 
     * @return gameState
     */
    public final GameState getGameState() {
        return gameState;
    }

    /**
     * Method that returns the arraylist "am" (user's missiles).
     * 
     * @return arraylist am
     */
    public final ArrayList<AllyMissile> getAllyMissiles() {
        return am;
    }

    /**
     * Method that returns the arraylist "em" (enemy's missiles).
     * 
     * @return arraylist em
     */
    public final ArrayList<EnemyMissile> getEnemyMissiles() {
        return em;
    }

    /**
     * Method that returns the arraylist "missileBays".
     * 
     * @return arraylist missileBays
     */
    public final ArrayList<MissileBay> getMissileBays() {
        return missileBays;
    }

    /**
     * Gives the number of cities which are alive.
     * 
     * @return number of alive cities
     */
    public final int getAliveCitiesCount() {
        int count = 0;
        // look for every city if it's alive
        for (int i = 0; i < cities.size(); i++) {
            if (cities.get(i).isAlive()) {

                // if it's alive add to the count value 1
                count++;
            }
        }

        return count;
    }

    /**
     * Revives all destroyed missileBay (called before each level).
     */
    public final void reviveAllMissileBays() {
        for (MissileBay missileBay : missileBays) {
            missileBay.setState(MissileBayState.ALIVE);
        }
    }

    /**
     * Loads the level from an xml file and returns true if the level loaded.
     * successful if there's not the level from the param value it returns
     * false.
     * 
     * @param level level
     * @return true if level exist either false
     */
    private boolean loadLevel(final int level) {
        // the min and max size of levels
        final int LEVEL_RANGE_MIN = 1;
        final int LEVEL_RANGE_MAX = 8;

        // sets the pointsAtStart at beginnign of the level
        pointsAtStart = points;

        // if the level has a number that is accepted
        if (level >= LEVEL_RANGE_MIN && level <= LEVEL_RANGE_MAX) {

            // sets the level in the level controller
            levelController.setLevel(level);

            //loads the right level from the xml file that
            levelController.loadFromFile("level" + level + ".xml");

            // calls the reviveAllMissileBays method
            reviveAllMissileBays();

            // starts the level controller
            levelController.start();

            // returns true
            return true;
        }

        // return false if the requested level from the param value does not
        // exist
        return false;
    }
    
    
    /**
     * With that the XML file of the levels can be created.
     * You add a test level controller a number of events you want,
     * and runs after that the
     * Tool_XMLWriter.writeLevelIntoXML("XMLDATEINAME.xml",
     * testController.getEvents()) method.
     * For calling this method, the best way is in the constructor
     * from board under LoadLevel().
     * Files are created in the first layer of the project folder,
     * from there you can move them with drag an drop in the
     * data folder of the project
     */
    public final void constructNewLevel() {
        LevelController testController = new LevelController(this);

        testController.addEvent(new Event(0.0f, EventType.SET_MISSILEBAYSTOCK,
                20));
        testController.addEvent(new Event(3.0f,
                EventType.CREATE_ENEMY_MISSILE_EVERYTHING));
        testController.addEvent(new Event(4.25f,
                EventType.CREATE_ENEMY_MISSILE_EVERYTHING));
        testController.addEvent(new Event(5.5f,
                EventType.CREATE_ENEMY_MISSILE_EVERYTHING));
        testController.addEvent(new Event(6.75f,
                EventType.CREATE_ENEMY_MISSILE_EVERYTHING));
        testController.addEvent(new Event(8.0f,
                EventType.CREATE_ENEMY_MISSILE_EVERYTHING));

     
        ToolXMLWriter.writeLevelIntoXML("level0.xml",
                testController.getEvents());
    }

}
